/**
 * Class Jsl.Observable
 * 
 * Base abstract class for creating observable objects.
 * It provides a common iterface for publishing events.
 * 
 * @author  Mykhailo Stadnyk <mikhus@gmail.com>
 * @package Jsl
 * @version 1.0
 */
Jsl.Observable = Jsl.extend( Object, {

	/**
	 * Collection of events
	 * 
	 * @type   {Jsl.Hash}
	 * @access protected
	 */
	events : null,

	/**
	 * Class constructor, expecting a config object containing
	 * a set of predefined event listeners
	 * 
	 * @param {Object} config
	 */
	constructor : function( config) {
		Jsl.Observable.$super.constructor.call( this);
		this.events = this.events || new Jsl.Hash;

		if (Jsl.isDefined( config) && Jsl.isObject( config.listeners))
			Jsl.each( config.listeners, function( l, e) {
				if (this.hasEvent( e) && Jsl.isFunction( l))
					this.events.get( e).merge( [l])
				;
			}, this)
		;
	},

	/**
	 * Returns true if current Observable instance contains at least one event,
	 * false otherwise.
	 *  
	 * @return {Boolean}
	 */
	hasEvents : function() {
		return (this.events.getCount() > 0);
	},

	/**
	 * Checks if current Observable has an event with passed event name
	 * 
	 * @param  {String} evt
	 * @return {Boolean}
	 */
	hasEvent : function( evt) {
		return this.events.isset( evt);
	},

	/**
	 * Checks if current Observable instance has at least one listener
	 * specified for an event with given name
	 * 
	 * @param  {String} evt
	 * @return {Boolean}
	 */
	hasListener : function( evt) {
		return !!(this.hasEvent( evt) && this.events.get( evt).length);
	},

	/**
	 * Fires an event with given name, bypassing to event handles specified list of arguments
	 * 
	 * @param  {String} evt                  - event name
	 * @param  {Mixed} [arg1, arg2..., argN] - as much arguments as you need to bypass to an event handler
	 * @return {Boolean}
	 */
	fireEvent : function() {
		var args = Jsl.arrize( arguments),
			evt  = args.shift(),
			ret  = true
		;
		if (this.hasListener( evt))
			Jsl.each( this.events.get( evt), function( l) {
				if (Jsl.isFunction( l) && (l.apply( this, args) === false))
					return (ret = false)
				;
			}, this)
		;
		return ret;
	},

	/**
	 * Adds an events to current Observable instance
	 * 
	 * @param  {Object} o = { evantName: {Array} handlers | {Function} handler | true },
	 * @return {Jsl.Observable}
	 * @access protected
	 */
	addEvents : function( o) {
		this.events = this.events || new Jsl.Hash;
		Jsl.each( o, function( ls, evt) {
			var ls = Jsl.isFunction( ls) ? [ls] :
				(Jsl.isArray( ls) ? ls : [])
			;
			if (this.hasEvent( evt))
				this.events.get( evt).merge( ls)
			; else
				this.events.set( evt, ls)
			;
		}, this);
		return this;
	},

	/**
	 * Adds an event handler to a specified event
	 * 
	 * @param  {String} evt
	 * @param  {Function} handler
	 * @return {Jsl.Observable}
	 */
	on : function( evt, handler) {
		if (this.hasEvent( evt) && Jsl.isFunction( handler))
			this.events.get( evt).merge( [handler])
		;
		return this;
	},

	/**
	 * Removes given handler from a specified event
	 * If handler is not specified - will remove all registered
	 * handlers for a given event
	 *  
	 * @param {String} evt
	 * @param {Function} handler - [Optional]
	 */
	un : function( evt, handler) {
		if (this.hasEvent( evt)) {
			var e = this.events.get( evt);
			if (Jsl.isFunction( handler))
				Jsl.each( e, function( l, indx) {
					if (l == handler)
						e.splice( indx, 1)
					;
				}, this)
			; else if (!Jsl.isDefined( handler))
				this.events.set( evt, [])
			;
		}
		return this;
	}

});
